--[[## ft=lua ##################################################################
  This file is licensed under [WTFPL v2](https://www.wtfpl.net/).  You may read
  the full license in the file LICENSE or by following the web link.

  PROXIFY - simple table proxying in Lua
--############################################################################]]


--[[####################################
  The general-purpose wrapper.  All other proxies are written in terms of this.
--####################################]]
local metametatable = {
    nope = 'not gonna happen'
}
local function wrap(tbl, idx, nidx, mt)

    assert(type(tbl) == 'table', 'only tables are proxified')
    assert(type(idx) == 'nil' or type(idx) == 'function', 'the index function must be absent or a function')
    assert(type(nidx) == 'nil' or type(nidx) == 'function', 'the newindex function must be absent or a function')
    assert(idx or nidx, 'at least one of the indexing operations must be supplied')
    assert(type(mt) == 'nil' or type(mt) == 'table', 'user-supplied metatable must be a table')

    local i, n

    if idx then
        i = function(_, k)
            return idx(tbl, k)
        end
    else
        i = function(_, k) return tbl[k] end
    end

    if nidx then
        n = function(_, k, v)
            nidx(tbl, k, v)
        end
    else
        n = function(_, k, v) tbl[k] = v end
    end

    local function l(_)
        return #tbl
    end

    if not mt then mt = {} end
    mt.__index = i;
    mt.__newindex = n;
    mt.__len = mt.__len or l
    mt.__metatable = metametatable

    return setmetatable({}, mt)
end

--[[####################################
  The read-only proxy.
--####################################]]
local function ro(tbl)
    n = function()
        error('attempt to update read-only table', 0)
    end
    return wrap(tbl, nil, n)
end

--[[####################################
  The access-logging proxy.
--####################################]]
local function logger(tbl, cb)
    local function i(t, k)
        cb('get', t, k, t[k])
        return t[k]
    end
    local function n(t, k, v)
        cb('set', t, k, v)
        t[k] = v
    end
    return wrap(tbl, i, n)
end

--[[####################################
  The counter proxy.
--####################################]]
local function counter(tbl)
    local count = 0
    for k,_ in pairs(tbl) do
        count = count + 1
    end

    local function n(t, k, v)
        if t[k] == nil and v ~= nil then
            count = count + 1
        elseif t[k] ~= nil and v == nil then
            count = count - 1
        end
        t[k] = v
    end

    local function l(_)
        return count
    end

    return wrap(tbl, nil, n, { __len = l })
end

local M = {
    wrap = wrap,
    ro = ro,
    logger = logger,
    counter = counter,
}

return setmetatable({}, { __index = M })
